#include "script/ScriptDocumentOperation.h"
#include "simodo/lsp-client/LspEnums.h"

using namespace simodo;

/// \file
/// \see https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_documentSymbol


variable::Value makeDocumentSymbolsResponse(
        const std::u16string & name,
        const std::u16string & detail,
        lsp::SymbolKind        kind,
        const lsp::Range &     range,
        const lsp::Range &     selectionRange
) {
    return variable::Object{{
        { u"name",           name },
        { u"detail",         detail },
        { u"kind",           static_cast<int64_t>(kind) },
        { u"range",          lsp::DocumentContext::makeRange(range) },
        { u"selectionRange", lsp::DocumentContext::makeRange(selectionRange) },
}};
}

/// Проверка на то, принадлежит ли данная переменная этому модулю (файлу)
bool isDeclaredVariableInCurrentFile(const variable::Variable & var) {
    const auto & spec_origin_value = var.spec().object()->find(variable::SPEC_PROPERTY);

    return spec_origin_value.isBool() and spec_origin_value.getBool();
}

variable::Value ScriptDocumentOperation::produceDocumentSymbolsResponse() const
{
    // \todo bug: дублируется результат в выпадающем списке,
    //       но, полагаю, проблема в дублирующихся перемеренных,
    //       которые получаются после семантического анализа
    //       (см. check/031-12-FunctionDefinition, check/031-14-FunctionDefinition)

    std::vector<variable::Value> doc_symbols_response;

    for (const auto & var : _semantic_data.declared()) {
        if (not isInCurrentFile(var.location())) continue;

        const bool isGlobal = isDeclaredVariableInCurrentFile(var);

        switch (var.type()) {
            case variable::ValueType::Function: {
                    const auto & body_range = getFunctionBodyRange(var);

                    const auto & var_start  = var.location().range().start();
                    // если имя функции находится левее тела функции, то возьмём вместе с именем функции, иначе просто тело
                    const auto & start      = var_start <= body_range.start() ? var_start : body_range.start();
                    const auto & end        = body_range.end();

                    doc_symbols_response.push_back(makeDocumentSymbolsResponse(
                        /* name           = */ var.name(),
                        /* detail         = */ getVariableString(var),
                        /* kind           = */ lsp::SymbolKind::Function,
                        /* range          = */ { start, end },
                        /* selectionRange = */ var.location().range()
                    ));
                }
                break;
            case variable::ValueType::Bool:
            case variable::ValueType::Int:
            case variable::ValueType::Float:
            case variable::ValueType::String:
            case variable::ValueType::Array:
                if (not isGlobal) break;
                doc_symbols_response.push_back(makeDocumentSymbolsResponse(
                    /* name           = */ var.name(),
                    /* detail         = */ getVariableString(var),
                    /* kind           = */ lsp::SymbolKind::Variable,
                    /* range          = */ var.location().range(),
                    /* selectionRange = */ var.location().range()
                ));
                break;
            case variable::ValueType::Null:
            case variable::ValueType::Object:
            case variable::ValueType::Ref:
            default: break;
        }

    }

    return doc_symbols_response;
}
